#include <string>
#include <vorbis/vorbisfile.h>
#include <boost/thread/recursive_mutex.hpp>
#include "Ogre.h"
#include "Callback.h"
#include "Resources.h"

#pragma once

#define NUM_BUFFERS 4

namespace UniverseSound 
{
	// Structure defining the wave sound format
	typedef struct
	{
		unsigned long	mFormatChunkSize,
						mDataSize,
						mSampleRate,
						mAvgBytesPerSecond,
						mAudioOffset,
						mChannelMask;
		
		unsigned short	mNumChannels,
						mBlockAlign,
						mBitsPerSample,
						mSamples;

		char			mSubFormat[16];
	} WavFormatData;

	// Defines the possible controls after a sound fading
	enum FadeControl
	{
		FC_NONE		= 0x00,
		FC_PAUSE	= 0x01,
		FC_STOP		= 0x02
	};

	// Structure defining the OGG sound format
	struct OggFile
	{
		char *DataPtr;		// Pointer to the data in memory
		int dataSize;		// Size of the data
		int dataRead;		// Size of data we have read per now
	};

	class SoundClip : public Ogre::MovableObject
	{
	public:
		// Open an audio file
		virtual void open(Ogre::DataStreamPtr& fileStream) = 0;
		// Open an audio file
		virtual void open(const Ogre::String& fileName, ALuint& buffer);
		// Set the audio source for playback
		virtual void setSource(ALuint& source) = 0;
		// Play th sound
		virtual void play() = 0;
		// Pause the sound
		virtual void pause() = 0;
		// Stop the sound
		virtual void stop() = 0;
		// Repeat the sound
		virtual void loop(bool loop);

		// True if the sound is currently playing
		bool isPlaying();
		// True if the sound is currently paused
		bool isPaused();
		// True if the sound is currently stopped
		bool isStopped();
		// True if the sound is positioned to the listener relatively
		bool isRelativeToListener();

		// Defines, if the source should be deleted when the sound is stopped
		void deleteSourceOnStop(bool dele = false);
		// Set the position of the sound
		void setPosition(float posx, float posy, float posz);
		// Set the position of the sound
		void setPosition(const Ogre::Vector3 &pos);
		// Set the direction of the sound
		void setDirection(const Ogre::Vector3 &dir);
		// Set the velocity of the sound
		void setVelocity(float velx, float vely, float velz);
		// Set the velocity of the sound
		void setVelocity(const Ogre::Vector3 &vel);
		// Set the volume of the sound
		void setVolume(float gain);
		// Returns the volume of the sound
		Ogre::Real getVolume();
		// Set the maximal volume of the sound
		void setMaxVolume(float maxGain);
		// Set the minimal volume of the sound
		void setMinVolume(float minGain);
		// Set the cone angles of the sound
		void setConeAngles(float insideAngle=360.f, float outsideAngle=360.f);
		// Set the volume at the outer cone
		void setOuterConeVolume(float gain=0.f);
		// Set the maximum distance of the sound
		void setMaxDistance(float maxDistance);
		// Set the rolloff factor of the sound
		void setRolloffFactor(float rolloffFactor);
		// Set the referance distance
		void setReferenceDistance(float referenceDistance);
		// Set the pitch of the sound
		void setPitch(float pitch);
		// Define if the sound should be relative to the listener
		void setRelativeToListener(bool relative);
		// Get the position of the sound
		Ogre::Vector3& getPosition();
		// Get the direction of the sound
		Ogre::Vector3& getDirection();
		// Start fading the sound
		void startFade(bool dir, Ogre::Real fadeTime, FadeControl actionOnCompleted=UniverseSound::FC_NONE);
		// True if the sound is fading
		bool isFading();

		// Update the sound
		virtual void update(Ogre::Real fTime);
		// Get the OpenAL source
		ALuint getSource();
		// Get the name of the sound
		const Ogre::String& getName() const {return mName;}
		// Get the priority of the sound
		Ogre::uint8 getPriority() {return mPriority;}
		// Set the priority of the sound
		void setPriority(Ogre::uint8 priority) { mPriority = priority; }
		// Set the play position of the sound
		virtual void setPlayPosition(Ogre::Real seconds);
		// Get the length of the sound clip
		Ogre::Real getAudioLength() {return mPlayTime;}
		// Get the movable type
		virtual const Ogre::String& getMovableType(void) const;
		// Get the bounding box
		virtual const Ogre::AxisAlignedBox& SoundClip::getBoundingBox() const;
		// Get the bounding radius
		virtual Ogre::Real getBoundingRadius(void) const;
		// Internal: Update the render queue
		virtual void _updateRenderQueue(Ogre::RenderQueue *queue);
		// Internal: Notify if the sound is attached
		virtual void _notifyAttached(Ogre::Node *node, bool isTagPoint=false);
		// Internal: Notify if the sound is moving
		virtual void _notifyMoved();
		// Visit the renderables
		virtual void visitRenderables(Ogre::Renderable::Visitor *visitor, bool debugRenderables);
		
		template<typename T>
		// Set the finished callback
		void setFinishedCallback(T *object, void(T::*function)(SoundClip *sound), bool enabled=true);
		// Set if the finished callback should be enabled
		void setFinishedCallbackEnabled(bool enable);
		// Set the loop callback
		template<typename T> void setLoopCallback(T *object, void(T::*function)(SoundClip *sound), bool enabled=true);
		// Set if the loop callback should be enabled
		void setLoopCallbackEnabled(bool enable);
		// Get the scene manager from UniverseGraphics
		Ogre::SceneManager *getSceneMgr();
		// Internal: True if the file is successfully loaded
		bool _isFileReady();

	private:
		// Release the sound
		virtual void _release() = 0;

	protected:
		boost::recursive_mutex mMutex;
		// Sound constructor
		SoundClip(const Ogre::String& name, bool seekSupport = false);
		// Sound destructor
		virtual ~SoundClip();
		// Internal: Initialize the source
		void _initSource();
		// Internal: Mark the play position
		void _markPlayPosition();
		// Internal: Recover the play position (see 'Listener::_markPlayPosition()')
		void _recoverPlayPosition();
		// Internal: Update the fade
		void _updateFade(Ogre::Real fTime=0.f);
		// Internal: Update the audio buffers
		virtual void _updateAudioBuffers() = 0;
		// Internal: Prebuffer the sound
		virtual void _prebuffer() = 0;
		// Internal: Query buffer information
		virtual bool _queryBufferInfo() = 0;
		
		Ogre::Real mFadeTimer;
		Ogre::Real mFadeTime;
		Ogre::Real mFadeInitVol;
		Ogre::Real mFadeEndVol;
		bool mFade;
		FadeControl mFadeEndAction;

		// Ogre resource stream pointer
		Ogre::DataStreamPtr mAudioStream;
		ov_callbacks mOggCallbacks;
		
		Callback* mLoopCB;
		Callback* mFinishedCB;
		bool mFinCBEnabled;
		bool mLoopCBEnabled;

		size_t mBufferSize;

		ALuint mSource;					// OpenAL Source
		Ogre::SceneManager* mSceneMgr;	// SceneManager pointer for plugin registered sounds
		Ogre::uint8 mPriority;			// Priority assigned to source 
		Ogre::Vector3 mPosition;		// 3D position
		Ogre::Vector3 mDirection;		// 3D direction
		Ogre::Vector3 mVelocity;		// 3D velocity
		Ogre::Real mGain;				// Current volume
		Ogre::Real mMaxGain;			// Minimum volume
		Ogre::Real mMinGain;			// Maximum volume
		Ogre::Real mMaxDistance;		// Maximum attenuation distance
		Ogre::Real mRolloffFactor;		// Rolloff factor for attenuation
		Ogre::Real mReferenceDistance;	// Half-volume distance for attenuation
		Ogre::Real mPitch;				// Current pitch 
		Ogre::Real mOuterConeVolume;		// Outer cone volume
		Ogre::Real mInnerConeAngle;		// Inner cone angle
		Ogre::Real mOuterConeAngle;		// outer cone angle
		Ogre::Real mPlayTime;			// Time in seconds of sound file
		Ogre::String mName;				// Sound name
		bool mLoop;						// Loop status
		bool mPlay;						// Play status
		bool mGiveUpSource;				// Flag to indicate whether sound should release its source when stopped
		bool mStream;					// Stream flag
		bool mFileOpened;				// File status flag (multi-threaded ONLY)
		bool mSourceRelative;			// Relative position flag
		bool mPlayDelayed;				// Queue play flag
		bool mStopDelayed;				// Queue stop flag
		bool mPauseDelayed;				// Queue pause flag
		bool mLocalTransformDirty;		// Transformation update flag
		bool mPlayPosChanged;			// Flag indicating playback position has changed
		bool mSeekable;					// Flag indicating seeking available

		ALfloat mPlayPos;				// Playback position in seconds

		friend class SoundManager;
	};
}